package main

import (
	"bufio"
	"encoding/csv"
	"flag"
	"io"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

type TableDMLNum struct {
	Insert int
	Update int
	Delete int
}

func init() {
	file := "./" + "bingo" + ".log"
	logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
	if err != nil {
		panic(err)
	}
	multiWriter := io.MultiWriter(os.Stdout, logFile)
	log.SetOutput(multiWriter)
	log.SetFlags(log.LstdFlags | log.LUTC)
	return
}

func main() {
	// 配置由 mysqlbinlog 解析好的日志文件路径
	binlogIn := flag.String("in", "binlog.txt", "binlog source path")
	binlogOut := flag.String("out", "binlog.csv", "binlog result file")
	flag.Parse()
	log.Println("binlog source path: ", *binlogIn)
	log.Println("binlog result file: ", *binlogOut)
	file, err := os.Open(*binlogIn)

	if err != nil {
		log.Fatalf("open file failed: %s \n", err.Error())
	}

	defer func(file *os.File) {
		err := file.Close()
		if err != nil {
			log.Fatalf("close file failed: %s \n", err.Error())
		}
	}(file)

	reader := bufio.NewReader(file)

	tableNameNumMap := make(map[string]TableDMLNum)
	tableNameNumTransMap := make(map[string]TableDMLNum)

	flagInSameTrans := false

	tableBeginRex := regexp.MustCompile(".*Table_map:.*mapped to number.*")
	tableUpdateRex := regexp.MustCompile("^### UPDATE .*")
	tableInsertRex := regexp.MustCompile("^### INSERT INTO .*")
	tableDeleteRex := regexp.MustCompile("^### DELETE FROM .*")
	tableCommitRex := regexp.MustCompile("^COMMIT\\/\\*\\!\\*\\/;$")

	log.Println("Binlog analysis transaction results：")
	log.Println("+-------------------------------------------------------------------------------------------+--------------------+--------------------+--------------------+")
	for {
		line, _, err := reader.ReadLine()

		if err == io.EOF {
			break
		}

		if tableBeginRex.Match(line) && !flagInSameTrans {
			flagInSameTrans = true
		}

		if flagInSameTrans && (tableUpdateRex.Match(line) || tableInsertRex.Match(line) || tableDeleteRex.Match(line)) {
			if tableUpdateRex.Match(line) {
				indexS := strings.Index(string(line), "### UPDATE") + len("### UPDATE") + 1
				tableName := strings.TrimSpace(string(line)[indexS:])
				if _, ok := tableNameNumTransMap[tableName]; !ok {
					tableNameNumTransMap[tableName] = TableDMLNum{Insert: 0, Update: 1, Delete: 0}
				} else {
					num := tableNameNumTransMap[tableName]
					num.Update = num.Update + 1
					tableNameNumTransMap[tableName] = num
				}
			} else if tableInsertRex.Match(line) {
				indexS := strings.Index(string(line), "### INSERT INTO") + len("### INSERT INTO") + 1
				tableName := strings.TrimSpace(string(line)[indexS:])
				if _, ok := tableNameNumTransMap[tableName]; !ok {
					tableNameNumTransMap[tableName] = TableDMLNum{Insert: 1, Update: 0, Delete: 0}
				} else {
					num := tableNameNumTransMap[tableName]
					num.Insert = num.Insert + 1
					tableNameNumTransMap[tableName] = num
				}
			} else if tableDeleteRex.Match(line) {
				indexS := strings.Index(string(line), "### DELETE FROM") + len("### DELETE FROM") + 1
				tableName := strings.TrimSpace(string(line)[indexS:])
				if _, ok := tableNameNumTransMap[tableName]; !ok {
					tableNameNumTransMap[tableName] = TableDMLNum{Insert: 0, Update: 0, Delete: 1}
				} else {
					num := tableNameNumTransMap[tableName]
					num.Delete = num.Delete + 1
					tableNameNumTransMap[tableName] = num
				}
			}
		}

		if tableCommitRex.Match(line) {
			flagInSameTrans = false
			for key, value := range tableNameNumTransMap {
				log.Printf("+ Transaction table: %-70s + Insert: %-10d + Update: %-10d + Delete: %-10d + \n", key, value.Insert, value.Update, value.Delete)
				if _, ok := tableNameNumMap[key]; ok {
					num := tableNameNumMap[key]
					num.Update = num.Update + value.Update
					num.Insert = num.Insert + value.Insert
					num.Delete = num.Delete + value.Delete
					tableNameNumMap[key] = num
				} else {
					tableNameNumMap[key] = TableDMLNum{Insert: value.Insert, Update: value.Update, Delete: value.Delete}
				}
			}
			if len(tableNameNumTransMap) > 0 {
				log.Println("+-------------------------------------------------------------------------------------------+--------------------+--------------------+--------------------+")
			}
			tableNameNumTransMap = make(map[string]TableDMLNum)
		}
	}

	f, err := os.OpenFile(*binlogOut, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)

	if err != nil {
		log.Fatalf("open file failed: %s \n", err.Error())
		return
	}
	writer := csv.NewWriter(f)

	var header = []string{"table", "insert", "update", "delete"}
	err2 := writer.Write(header)

	if err2 != nil {
		log.Fatalf("write file failed: %s \n", err2.Error())
		return
	}

	log.Println("Binlog analysis total results：")
	log.Println("+--------------------------------------------------------------------------------+--------------------+--------------------+--------------------+")
	for key, value := range tableNameNumMap {
		log.Printf("+ Total table: %-65s + Insert: %-10d + Update: %-10d + Delete: %-10d + \n", key, value.Insert, value.Update, value.Delete)
		log.Println("+--------------------------------------------------------------------------------+--------------------+--------------------+--------------------+")
		var data = []string{key, strconv.Itoa(value.Insert), strconv.Itoa(value.Update), strconv.Itoa(value.Delete)}
		err3 := writer.Write(data)
		if err3 != nil {
			log.Fatalf("write file failed: %s \n", err3.Error())
			return
		}
		writer.Flush()
	}

}
